<?php

/***  DOCUMENTATION LAYER

Greqo Blogger Class

Name: GreqoBlogger
Version: 1.0
Last Update: May 2007
Author: Tom at klenwell@gmail.com

DESCRIPTION
	PHP4-compatible class for utilizing the Google Blogger API

METHODS
	MAGIC
	GreqoBlogger($debug=0)	*php 4 constructor*
	__construct($debug)		*php 5 constructor*
	__destruct()	
	
	PUBLIC
	request_feed($num_entries=15, $start=1, $type='full')
	post_entry($body, $title='', $blog_id=0)
	edit_entry($entry_id, $body, $title='')
	delete_entry($entry_id)
	request_blog_list()
	request_scope($force=0)
	set_service_scope($url)
	
	PRIVATE
	_parse_post_response($xml_string)
	_set_filename()
	_set_dirpath()
	
USAGE
	$Class = new ClassClass();
	$Class->print_r('hello world']);

NOTES
	TO DO: cache (using PEAR cache_lite)
	
	Explanation of difference between service url and service scope.  Both are urls.
	But Google will sometimes change the URLs to which a particular service request
	is directed.  The service url is a constant url which can be queried to find
	out what is the current scope (url for specific service requests).

______________________________________________________________________________*/

// Load File of Base Class
$base_fname = 'greqo.class.php';
$base_dirpath = '';
require_once($base_dirpath . $base_fname);


// GreqoBlogger
/*____________________________________________________________________________*/
class GreqoBlogger extends Greqo
{
/* PUBLIC PROPERTIES */
var $debug = 0;
var $class_name = __CLASS__;
var $oid = '';
var $DS = DIRECTORY_SEPARATOR;

var $service = 'blogger';
var $service_url = 'http://www.blogger.com/feeds/default/blogs';
var $service_scope = '';
var $blog_id = 0;
var $content_type = 'html';

// session values
#$_SESSION['GDATA']['BLOGGER']['service_scope'] = '';

/* PRIVATE PROPERTIES */
var $_filename = '';
var $_dirpath = '';


/* ** MAGIC METHODS ** */
// php4 constructor
function GreqoBlogger($debug=0, $oid=null)
{
	$this->__construct($debug, $oid);
	register_shutdown_function( array($this, '__destruct') );
}
// END constructor

// php5 constructor
function __construct($debug=0, $oid=null)
{
	// default
	$this->debug = $debug;
	$this->oid = ( empty($oid) ) ? $this->class_name : $oid;
	$this->_set_filename();
	$this->_set_dirpath();
	
	parent::__construct($debug, $oid); 
}
// END constructor

// destructor
function __destruct()
{
	if ( $this->debug ) $this->print_d("destroying class {$this->class_name} (oid: {$this->oid})");
}
// END destructor



/* ** PUBLIC METHODS ** */
// method: request feed
function request_feed($blog_id=0, $num_entries=15, $start=1, $type='full')
{
	$FEED_ARRAY = array();		// return
	$method = HTTP_REQUEST_METHOD_GET;
	
	// set blog id
	$this->blog_id = ( !empty($blog_id) ) ? $blog_id : $this->blog_id;

	// prep check
	if ( !$this->_is_blog_request_prepared() ) return 0;
  
  // create query array
  $QUERY = array(
    'start-index' => $start,
    'max-results' => $num_entries
  );
  
  return $this->request_feed_by_query_array($blog_id, $type, $QUERY);

}



// method: request feed
function request_feed_by_query_array($blog_id=0, $type='default', $QUERY=array())
{
	$FEED_ARRAY = array();		// return
	$method = HTTP_REQUEST_METHOD_GET;
	
	// set blog id
	$this->blog_id = ( !empty($blog_id) ) ? $blog_id : $this->blog_id;

	// prep check
	if ( !$this->_is_blog_request_prepared() ) return 0;
  
  // prepare query
  $request_query = '';
  if ( !empty($QUERY) )
  {
    foreach ( $QUERY as $k => $v ) $Q[] = "{$k}=" . urlencode($v);
    $request_query = '?' . implode('&', $Q);
  }

	// request scope, set request url
	if ( !$this->request_scope($force=1) ) return 0;
	$request_url = $this->service_scope;
	$request_url .= "/feeds/{$this->blog_id}/posts/{$type}{$request_query}";
	
	// get authorization token
	if ( !$auth_token = $this->request_clientlogin_token($this->service, $force=0) ) 
	{
		$this->_trigger_request_error('unable to retrieve auth token');
		return 0;
	}
	
	// prepare and send request
	$this->GreqoClient->setURL($request_url);
	$this->GreqoClient->setMethod($method);
	$this->GreqoClient->addHeader('authorization', 'GoogleLogin auth=' . $auth_token);
	$this->_send_client_request();
	
	// procress response
	if ( $this->GreqoResponse->code_class == 2 )
  {
		$FEED_ARRAY = $this->atom_to_array($this->GreqoResponse->body);
  }
	
	// return
	return $FEED_ARRAY;
}



// method: post entry
function post_entry($body, $title='', $blog_id=0, $rfc3339='')
{
	$ENTRY_DATA = array();		// return
	$method = HTTP_REQUEST_METHOD_POST;

	// set blog id
	$this->blog_id = ( !empty($blog_id) ) ? $blog_id : $this->blog_id;
	
	if ( $this->debug ) $this->print_d('attempting to post entry to blog ' . $this->blog_id);	

	// prep check
	if ( !$this->_is_blog_request_prepared() ) return 0;	

	// request scope, set request url
	if ( !$this->request_scope($force=0) ) return 0;
        
        // customize date
        $published = ( !empty($rfc3339) ) ? "\n  <published>{$rfc3339}</published>" : '';
	
	// set request url
	$request_url = $this->service_scope . "/feeds/{$this->blog_id}/posts/full";
	
	// create atom xml
	$safe_title = $this->escape_entities($title);
	$safe_body = $this->escape_entities($body);
	$atom_data = <<<XML
<entry xmlns="http://www.w3.org/2005/Atom">
  <title type="{$this->content_type}">$safe_title</title>{$published}
  <content type="{$this->content_type}">$safe_body</content>
</entry>
XML;
	
	// get authorization token
	if ( !$auth_token = $this->request_clientlogin_token($this->service, $force=0) ) 
	{
		$this->_trigger_request_error('unable to retrieve auth token');
		return 0;
	}
	
	// prepare request
	$this->GreqoClient->setURL($request_url);
	$this->GreqoClient->setMethod($method);
	$this->GreqoClient->addHeader('Content-type', 'application/atom+xml');
	$this->GreqoClient->addHeader('authorization', 'GoogleLogin auth=' . $auth_token);
	$this->GreqoClient->addRawPostData($atom_data, 1);
	
	// send request
	if ( !$this->_send_client_request() ) return 0;

	// request successful
	if ( $this->GreqoResponse->code_class == 2 )
	{
		if ( !$ENTRY_DATA = $this->_parse_post_response($this->GreqoResponse->body) )
		{
			$this->_trigger_request_error('unable to parse post response');
			return 0;
		}
	}
	
	// return data
	return $ENTRY_DATA;
}
// END method

// method: edit entry
function edit_entry($entry_id, $body, $title='', $blog_id=0)
{
	$ENTRY_DATA = array();		// return
	$method = HTTP_REQUEST_METHOD_PUT;

	// set blog id
	$this->blog_id = ( !empty($blog_id) ) ? $blog_id : $this->blog_id;
	
	if ( $this->debug ) $this->print_d('attempting to update entry #$entry_id on blog ' . $this->blog_id);	

	// prep check
	if ( !$this->_is_blog_request_prepared() ) return 0;	

	// request scope, set request url
	if ( !$this->request_scope($force=0) ) return 0;
	
	// set request url
	$request_url = $this->service_scope . "/feeds/{$this->blog_id}/posts/full/$entry_id";
	
	// create atom xml
	$safe_title = $this->escape_entities($title);
	$safe_body = $this->escape_entities($body);
	$atom_data = <<<XML
<entry xmlns="http://www.w3.org/2005/Atom">
  <title type="{$this->content_type}">$safe_title</title>
  <content type="{$this->content_type}">$safe_body</content>
</entry>
XML;
	
	// get authorization token
	if ( !$auth_token = $this->request_clientlogin_token($this->service, $force=0) ) 
	{
		$this->_trigger_request_error('unable to retrieve auth token');
		return 0;
	}
	
	// prepare request
	$this->GreqoClient->setURL($request_url);
	$this->GreqoClient->setMethod($method);
	$this->GreqoClient->addHeader('Content-type', 'application/atom+xml');
	$this->GreqoClient->addHeader('authorization', 'GoogleLogin auth=' . $auth_token);
	$this->GreqoClient->addRawPostData($atom_data, 1);
	
	// send request
	if ( !$this->_send_client_request() ) return 0;

	// request successful
	if ( $this->GreqoResponse->code_class == 2 )
	{
		if ( !$ENTRY_DATA = $this->_parse_post_response($this->GreqoResponse->body) )
		{
			$this->_trigger_request_error('unable to parse post response');
			return 0;
		}
	}
	
	// return data
	return $ENTRY_DATA;
}
// END method

// method: delete entry
function delete_entry($entry_id, $blog_id=0)
{
	$is_deleted = 0;		// return
	$method = HTTP_REQUEST_METHOD_DELETE;

	// set blog id
	$this->blog_id = ( !empty($blog_id) ) ? $blog_id : $this->blog_id;
	
	if ( $this->debug ) $this->print_d('attempting to delete entry #$entry_id on blog ' . $this->blog_id);	

	// prep check
	if ( !$this->_is_blog_request_prepared() ) return 0;	

	// request scope, set request url
	if ( !$this->request_scope($force=0) ) return 0;
	
	// set request url
	$request_url = $this->service_scope . "/feeds/{$this->blog_id}/posts/full/$entry_id";
	
	// get authorization token
	if ( !$auth_token = $this->request_clientlogin_token($this->service, $force=0) ) 
	{
		$this->_trigger_request_error('unable to retrieve auth token');
		return 0;
	}
	
	// prepare request
	$this->GreqoClient->setURL($request_url);
	$this->GreqoClient->setMethod($method);
	$this->GreqoClient->addHeader('authorization', 'GoogleLogin auth=' . $auth_token);
	
	// send request
	if ( !$this->_send_client_request() ) return 0;

	// request successful
	if ( $this->GreqoResponse->code_class == 1 || $this->GreqoResponse->code_class == 2 )
	{
		$is_deleted = 1;
	}
	else
	{
		$this->_trigger_request_error("unable to to delete entry #$entry_id");
	}
	
	// return data
	return $is_deleted;
}
// END method



// method: post entry 
// see http://code.google.com/apis/blogger/developers_guide_protocol.html#CreatingComments
function post_comment($post_id, $comment, $author='')
{
	$COMMENT_DATA = array();		// return
	$method = HTTP_REQUEST_METHOD_POST;

	// set author
  if ( empty($author) ) $author = $this->email;
	
	if ( $this->debug ) $this->print_d('attempting to post comment to blog ' . $this->blog_id);	

	// prep check
	if ( !$this->_is_blog_request_prepared() ) return 0;	

	// request scope, set request url
	if ( !$this->request_scope($force=0) ) return 0;
	
	// set request url
	$request_url = $this->service_scope . "/feeds/{$this->blog_id}/{$post_id}/comments/default";
	
	// create atom xml
	$safe_author = $this->escape_entities($author);
	$safe_comment = $this->escape_entities($comment);
	$atom_data = <<<XML
<entry xmlns='http://www.w3.org/2005/Atom'>
  <title type="text">$safe_comment</title>
  <content type="html">$safe_comment</content>
  <author>
    <name>$safe_author</name>
  </author>
</entry>
XML;
	
	// get authorization token
	if ( !$auth_token = $this->request_clientlogin_token($this->service, $force=0) ) 
	{
		$this->_trigger_request_error('unable to retrieve auth token');
		return 0;
	}
	
	// prepare request
	$this->GreqoClient->setURL($request_url);
	$this->GreqoClient->setMethod($method);
	$this->GreqoClient->addHeader('Content-type', 'application/atom+xml');
	$this->GreqoClient->addHeader('authorization', 'GoogleLogin auth=' . $auth_token);
	$this->GreqoClient->addRawPostData($atom_data, 1);
	
	// send request
	if ( !$this->_send_client_request() ) return 0;

	// request successful
	if ( $this->GreqoResponse->code_class == 2 )
	{
		if ( !$COMMENT_DATA = $this->_parse_post_response($this->GreqoResponse->body) )
		{
			$this->_trigger_request_error('unable to parse post response');
			return 0;
		}
	}

	return $COMMENT_DATA;
}



// method: request blog list
function request_blog_list()
{
	$BLOG_LIST = array();		// return
	$method = HTTP_REQUEST_METHOD_GET;
	
	// set blog id
	$this->blog_id = ( !empty($blog_id) ) ? $blog_id : $this->blog_id;

	// prep check
	if ( !$this->_is_login_prepared() ) return 0;	

	// request scope, set request url
	if ( !$this->request_scope($force=1) ) return 0;
	$request_url = $this->service_scope . '/feeds/default/blogs';
	
	// get authorization token
	if ( !$auth_token = $this->request_clientlogin_token($this->service, $force=0) ) 
	{
		$this->_trigger_request_error('unable to retrieve auth token');
		return 0;
	}
	
	// prepare and send request
	$this->GreqoClient->setURL($request_url);
	$this->GreqoClient->setMethod($method);
	$this->GreqoClient->addHeader('authorization', 'GoogleLogin auth=' . $auth_token);
	$this->_send_client_request();
	
	// process response
	if ( $this->GreqoResponse->code_class == 2 )
  {
		$BLOG_LIST = $this->atom_to_array($this->GreqoResponse->body);
  }
	
	// return
	return $BLOG_LIST;
}
// END method

// method: request scope
function request_scope($force=0)
{
	$scope = '';		// return
	
	if ( $this->debug ) $this->print_d('requesting scope');
	
	// Prime Session value
	$_SESSION['GDATA']['BLOGGER']['service_scope'] = ( !empty($_SESSION['GDATA']['BLOGGER']['service_scope']) ) ? $_SESSION['GDATA']['BLOGGER']['service_scope'] : '';

	// Is scope already set?
	if ( !empty($_SESSION['GDATA']['BLOGGER']['service_scope']) && !$force ) 
	{
		if ( $this->debug ) $this->print_d("service scope is already set: {$_SESSION['GDATA']['BLOGGER']['service_scope']}", 'blue');
		$this->set_service_scope($_SESSION['GDATA']['BLOGGER']['service_scope']);
		return $_SESSION['GDATA']['BLOGGER']['service_scope'];
	}
	
	// request session auth token
	if ( !$this->request_clientlogin_token($this->service, $force=0) ) return 0;
	
	// prepare request
	$this->GreqoClient->_allowRedirects = 0;
	$this->GreqoClient->setURL($this->service_url);
	$this->GreqoClient->setMethod(HTTP_REQUEST_METHOD_GET);
	$this->GreqoClient->addHeader('Authorization', 'GoogleLogin auth=' . $this->gdata_auth_token);
	
	// temporarily disable redirects, if allowed
	$old_redirect = $this->allow_redirect;
	$this->allow_redirect = 0;
	
	// send request
	$this->_send_client_request();
	
	// parse results, set scope
	if ( $this->GreqoResponse->code_class == 2 )
	{
		if ( preg_match("|(http://[^/]+)|si", $this->service_url, $_MATCH) ) $this->set_service_scope($_MATCH[1]);
		if ( $this->debug ) $this->print_d("service scope set to {$_MATCH[1]}", 'green');
	}
	// redirect (300 code)
	elseif ( $this->GreqoResponse->code_class == 3 )
	{
		if ( !$_location = $this->GreqoClient->getResponseHeader('Location') )
		{
			$this->_trigger_request_error("redirect code({$this->GreqoResponse->code}) : no location in response");
			return 0;
		}
		if ( preg_match("|(http://[^/]+)|si", $_location, $_MATCH) ) $this->set_service_scope($_MATCH[1]);
		if ( $this->debug ) $this->print_d("service scope set to redirect location: {$_MATCH[1]}", 'yellow');
	}
	
	$this->allow_redirect = $old_redirect;
	
	$scope = $this->service_scope;
	return $scope;
}
// END method

// method: set_service_scope
function set_service_scope($url)
{
	$this->service_scope = $url;
	$_SESSION['GDATA']['BLOGGER']['service_scope'] = $url;
	return;
}
// END method



/* ** PRIVATE METHODS ** */
// method: parse post response
function _parse_post_response($xml_string) 
{
	// return
	$POST_DATA = array
	(
		'id' => 0,
		'self_link' => '',
		'edit_link' => '',
	);		

	// regex expressions
	$link_regex = "%<link\s*rel='[^']*'\s*type='[^']*'\s*href='([^']*)'%i";
	#$id_regex = '%<id>(.*(?=</id>))%i';

	// regex
	if ( !preg_match_all($link_regex, $xml_string, $_LINK) )
	{
		trigger_error('unable to parse response:' . htmlspecialchars($xml_string), E_USER_NOTICE);
		return 0;
	}
	
	$self_link = $_LINK[1][0];
	$edit_link = $_LINK[1][1];
	$id = $this->_extract_post_id($edit_link);
	
	$POST_DATA = array
	(
		'id' => $id,
		'self_link' => $self_link,
		'edit_link' => $edit_link,
	);

	return $POST_DATA;

}
// END method

// method: is blog request prepared
function _is_blog_request_prepared()
{
	if ( !$this->_is_login_prepared() ) return 0;
	if ( empty($this->blog_id) )
	{
		$this->_trigger_request_error('blog id must be set');
		return 0;
	}
	return 1;
}



function _extract_post_id($edit_link)
{
    // currently: http://www.blogger.com/comment.g?blogID=1583289811561441106&postID=1126341054634043292
    $PARTS = explode('postid=', strtolower($edit_link));
    return $PARTS[1];
}
  
  

function _set_filename() { $this->_filename = basename(__FILE__); }
function _set_dirpath() { $this->_dirpath = dirname(__FILE__) . $this->DS; }

} // end class
/*____________________________________________________________________________*/

?>
